package com.ji.li.reactadminproexampleservice.utils;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ji.li.reactadminproexampleservice.constants.SecurityConstants;
import com.ji.li.reactadminproexampleservice.utils.type.CurrentUserUtils;
import com.ji.li.reactadminproexampleservice.utils.type.JsonUtils;
import com.ji.li.reactadminproexampleservice.utils.type.JwtUtils;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

@Component
public class UtilsImpl implements JsonUtils, JwtUtils, CurrentUserUtils {

    /**
     * @param object java object
     * @return  JSONObject or JSONArray
     */
    public JSON Bean2Json(Object object) {
        if (object instanceof ArrayList) {
            return JSONArray.parseArray(JSONArray.toJSONString(object));
        }
        return JSON.parseObject(JSON.toJSONString(object));
    }

    /**
     * @param json json
     * @param t java object type
     * @param <T> java object type
     * @return java object type
     */
    public <T> T toJavaObject (JSON json, Class<T> t) {
        if (json instanceof JSONObject) {
            return json.toJavaObject(t);
        }
        return null;
    }

    /**
     * @param json json array
     * @param t java object type
     * @param <T> java object type
     * @return java list<object>
     */
    public <T> List<T> toJavaList (JSON json, Class<T> t) {
        if (json instanceof JSONArray) {
            return ((JSONArray) json).toJavaList(t);
        }
        return null;
    }

    @Override
    public JSONObject errorMessage(String error) {
        JSONObject jsonObject = new JSONObject();
        if (error == null)
            jsonObject.put("error", "noMessage");
        else
            jsonObject.put("error", error);
        return jsonObject;
    }

    @Override
    public String createToken(String username, int id, List<String> roles, boolean isRememberMe) {
        long expiration = isRememberMe ? EXPIRATION_REMEMBER : EXPIRATION;
        final Date createdDate = new Date();
        final Date expirationDate = new Date(createdDate.getTime() + expiration * 1000);
        return Jwts.builder()
                .setHeaderParam("type", SecurityConstants.TOKEN_TYPE)
                .signWith(SECRET_KEY, SignatureAlgorithm.HS256)
                .claim(SecurityConstants.ROLE_CLAIMS, String.join(",", roles))
                .setId(String.valueOf(id))
                .setIssuer("ji.li")
                .setIssuedAt(createdDate)
                .setSubject(username)
                .setExpiration(expirationDate)
                .compact(); // 直接返回token
    }

    @Override
    public String getU_id(String token) {
        Claims claims = getClaims(token);
        return claims.getId();
    }

    @Override
    public UsernamePasswordAuthenticationToken getAuthentication(String token) {
        Claims claims = getClaims(token);
        List<SimpleGrantedAuthority> authorities = getAuthorities(claims);
        String userName = claims.getSubject();
        return new UsernamePasswordAuthenticationToken(userName, token, authorities);
    }

    private static List<SimpleGrantedAuthority> getAuthorities(Claims claims) {
        String role = (String) claims.get(SecurityConstants.ROLE_CLAIMS);
        return Arrays.stream(role.split(","))
                .map(SimpleGrantedAuthority::new)
                .collect(Collectors.toList());
    }

    private static Claims getClaims(String token) {
        return Jwts.parser()
                .setSigningKey(SECRET_KEY)
                .parseClaimsJws(token)
                .getBody();
    }

    @Override
    public String setRedisKey(int uid) {
        return "react_pro_" + uid + "_token";
    }

    @Override
    public String getCurrentUser() {
        return getCurrentUserName();
    }

    private String getCurrentUserName() {
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        if (authentication != null && authentication.getPrincipal() != null) {
            return (String) authentication.getPrincipal();
        }
        return null;
    }
}
